package com.orcwar.ai.naive;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import android.util.Log;

import com.orcwar.engine.OWGame;
import com.orcwar.engine.OWItem;
import com.orcwar.engine.OWMap;
import com.orcwar.engine.OWPlayer;
import com.orcwar.engine.OWTile;
import com.orcwar.engine.OWUnit;
import com.orcwar.engine.pathfinding.AStarPathFinder;
import com.orcwar.engine.pathfinding.TileScanner;
import com.orcwar.engine.tile.OWAttackTile;
import com.orcwar.enums.OWAttackTileTypeEnum;

public class NaiveAI extends com.orcwar.ai.OWAI
{
	/** Logger tag. */
	private static final String TAG = NaiveAI.class.getSimpleName();
	
	/** Heuristic score when moving. */
	private static final int SCORE_MOVE = -1;
	/** Heuristic score when the unit kills an enemy unit. */
	private static final int SCORE_KILL = 100;
	/** Heuristic score when the unit is within range of an enemy unit. */
	private static final int SCORE_DEATH = -150;
	/** Heuristic score for each item stack and quality point. */
	private static final int SCORE_ITEM = 10;
	
	/** The AI's enemies, i.e. all players except the AI. */
	private Set<OWPlayer> enemies;
	
	/** The AI's enemies' units. */
	private Set<OWUnit> enemyUnits;
	
	/** The A* pathfinder. */
	private AStarPathFinder pathfinder;
	
	/** Position (tile) of each unit. */
	private Map<OWUnit, OWTile> unitPositions;
	
	/**
	 * Initializes the AI.
	 * 
	 * @param game the game engine.
	 * @param player the player that this AI plays for.
	 */
	public NaiveAI(OWGame game, OWPlayer player)
	{
		super(game, player);
		this.pathfinder = new AStarPathFinder(game.getMap(), false);
		this.unitPositions = new HashMap<OWUnit, OWTile>();
		
		this.enemies = new HashSet<OWPlayer>(game.getPlayers());
		this.enemies.remove(player);
		this.enemyUnits = new HashSet<OWUnit>();
	}
	
	@Override
	public void onLevelStart(OWMap map)
	{
		enemyUnits.clear();
		unitPositions.clear();
		
		for(OWPlayer enemy : enemies)
		{
			enemyUnits.addAll(enemy.getUnits());
		}
		
		for(OWUnit unit : player.getUnits())
		{
			unitPositions.put(unit, unit.getTile());
		}
		for(OWUnit enemyUnit : enemyUnits)
		{
			unitPositions.put(enemyUnit, enemyUnit.getTile());
		}
	}
	
	@Override
	public void onUnitMove(OWUnit unit, OWTile startTile, OWTile endTile,
			List<OWTile> path)
	{
		unitPositions.put(unit, endTile);
	}
	
	@Override
	public void onUnitDeath(OWUnit unit)
	{
		unitPositions.remove(unit);
		enemyUnits.remove(unit);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void doTurn()
	{
		Log.d(TAG, "Starting enemy's turn");
		for(OWUnit unit : player.getUnits())
		{
			doUnitTurn(unit);
		}
		Log.d(TAG, "Ending enemy's turn");
		game.pass(player);
		return;
	}
	
	private void doUnitTurn(OWUnit unit)
	{
		Log.d(TAG, "Starting to play unit " + unit);
		
		// Searching reachable units
		Set<OWUnit> reachableUnits = new HashSet<OWUnit>();
		Set<OWTile> possibleTilesToMove = TileScanner.findPossibleTilesToMove(
				game.getMap(), unit, pathfinder);
		Set<OWAttackTile> possibleTilesToAttack = TileScanner.findPossibleTilesToAttack(
				game.getMap(), unit, possibleTilesToMove);
		for(OWUnit enemyUnit : enemyUnits)
		{
			if(possibleTilesToAttack.contains(unitPositions.get(enemyUnit)))
			{
				reachableUnits.add(enemyUnit);
			}
		}
		
		// Doing action depending on whether there are units within range
		if(reachableUnits.size() == 0)
		{
			moveTowardsClosestEnemyUnit(unit);
		}
		else
		{
			moveAndAttack(unit, possibleTilesToMove);
		}
	}
	
	private void moveTowardsClosestEnemyUnit(OWUnit unit)
	{
		// Search enemy units for the closest one
		OWUnit target = null;
		List<OWTile> targetPath = new ArrayList<OWTile>(0);
		int targetDistance = Integer.MAX_VALUE;
		for(OWUnit enemyUnit : enemyUnits)
		{
			List<OWTile> path = pathfinder.findPath(unit,
					unitPositions.get(unit).getX(),
					unitPositions.get(unit).getY(),
					unitPositions.get(enemyUnit).getX(),
					unitPositions.get(enemyUnit).getY(), false);
			if(path != null && path.size() < targetDistance)
			{
				target = enemyUnit;
				targetPath = path;
				targetDistance = path.size();
			}
		}
		
		// Move towards that unit
		Log.d(TAG, "Unit " + unit + " is hunting " + target);
		game.move(unit, targetPath.subList(0, Math.min(targetPath.size(),
				unit.getMovePoints())));
	}
	
	private void moveAndAttack(OWUnit unit, Set<OWTile> possibleTilesToMove)
	{
		// Evaluates each possible move
		int bestScore = Integer.MIN_VALUE;
		OWTile move = unitPositions.get(unit);
		Set<OWTile> targets = new HashSet<OWTile>();
		
		Set<OWUnit> unitsInRange = new HashSet<OWUnit>();
		for(OWTile tile : possibleTilesToMove)
		{
			// Search units that can be attacked from the current tile
			unitsInRange.clear();
			for(OWUnit enemyUnit : enemyUnits)
			{
				if(unit.isRangeOk(unitPositions.get(unit).getX(),
						unitPositions.get(unit).getY(),
						unitPositions.get(enemyUnit).getX(),
						unitPositions.get(enemyUnit).getY()))
				{
					unitsInRange.add(enemyUnit);
				}
			}
			
			// Evaluates each possible attack from the current tile
			for(Set<OWTile> attack :
					getPossibleAttacks(unit.getAttackPoints(), unitsInRange))
			{
				int score = evaluateAction(unit, tile, attack);
				if(score > bestScore)
				{
					move = tile;
					targets = attack;
					bestScore = score;
					Log.d(TAG, "Found a better action scoring " + bestScore
							+ ": move to " + move + " and attack " + targets);
				}
			}
		}
		
		// Perform the best action (move + attacks)
		Log.d(TAG, "Unit " + unit + " is about to attack " + targets);
		game.move(unit, pathfinder.findPath(unit,
				unitPositions.get(unit).getX(), unitPositions.get(unit).getY(),
				move.getX(), move.getY()));
		for(OWTile targetTile : targets)
		{
			//TODO: CHAAAAAAAAAAAAAAAAAAAAAAAANGE ! Need to pass a OWAttackTile from the beginning
			game.attack(unit, OWAttackTile.create(targetTile, OWAttackTileTypeEnum.ATTACKABLE));
		}
	}
	
	private Set<Set<OWTile>> getPossibleAttacks(int attackPoints,
			Set<OWUnit> unitsInRange)
	{
		// TODO return all combinations
		
		Set<Set<OWTile>> possibleAttacks = new HashSet<Set<OWTile>>();
		Set<OWTile> attack = new HashSet<OWTile>();
		int unitCount = 0;
		for(OWUnit unit : unitsInRange)
		{
			if(++unitCount > attackPoints)
			{
				break;
			}
			attack.add(unitPositions.get(unit));
		}
		possibleAttacks.add(attack);
		return possibleAttacks;
	}
	
	private int evaluateAction(OWUnit unit, OWTile moveTile,
			Set<OWTile> attackTiles)
	{
		int score = 0;
		
		if(moveTile.getCurrentUnit() == null
				|| ! moveTile.getCurrentUnit().equals(unit))
		{
			score += SCORE_MOVE;
		}
		for(OWUnit enemyUnit : enemyUnits)
		{
			// Evaluates enemy units to be killed
			if(attackTiles.contains(unitPositions.get(enemyUnit)))
			{
				score += SCORE_KILL;
				if(enemyUnit.getItems() != null)
				{
					for(OWItem item : enemyUnit.getItems())
					{
						score += SCORE_ITEM * item.getType().getQuality()
								* item.getStacks();
					}
				}
			}
			// Evaluates enemy units that can kill the unit
			else if(enemyUnit.isRangeOk(unitPositions.get(enemyUnit).getX(),
					unitPositions.get(enemyUnit).getY(),
					moveTile.getX(), moveTile.getY()))
			{
				score += SCORE_DEATH;
			}
		}
		
		return score;
	}
}
